package com.owg.jigsaw;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.Queue;

import com.owg.jigsaw.hierarchy.TypeNode;
import com.owg.jigsaw.javafx.ActionSource;
import com.owg.jigsaw.javafx.FXBlock;
import com.owg.jigsaw.javafx.FXUtil;
import com.owg.jigsaw.slots.Slot;

import owg.util.data.PFieldImmutable;
import owg.util.data.ReferenceSet;
import owg.util.data.Referencer;
import owg.util.data.property.Property;
import owg.util.data.property.PropertyReadOnly;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ObjectPropertyBase;
import javafx.geometry.Point2D;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;

public abstract class WBlockBase implements WBlock
{
	@SuppressWarnings({"unchecked", "rawtypes"})
	private final Property<WSingleFieldContainer<WBlock>> container = (Property<WSingleFieldContainer<WBlock>>) new Property(WContainer.class, null);
	public final Property<TypeNode> tabType = new Property<>(TypeNode.class, null);
	public final ObjectProperty<Color> color = new ObjectPropertyBase<Color>(Color.GRAY)
		{
			@Override
			public Object getBean()
			{
				return WBlockBase.this;
			}
			@Override
			public String getName()
			{
				return "Block Color";
			}
		};
	public final ActionSource destructionListeners = new ActionSource();
	
	protected final WRoot root;
	protected FXBlock pane;
	private final LinkedHashSet<CellListener> cellListeners = new LinkedHashSet<>();
	public final boolean isPaletteBlock;
	protected final Referencer referencer = new ReferenceSet();
	
	protected final ArrayList<ArrayList<WCell>> table;
	
	public WBlockBase(WBlockBase src)
	{
		this(src.getRoot(), src.tabType.get().getKey(), false, src.color.get());
		
	}
	
	public WBlockBase(WRoot root, Object typeKey, boolean isPaletteBlock, Color defaultColor)
	{
		this.root = root;
		//Note: getTypeNode() is fail-fast.
		this.tabType.set(root.getTypeHierarchy().getTypeNode(typeKey));
		//Eject blocks from slot if their type is no longer compatible
		tabType.addListener(referencer, (param, oldVal, newVal) ->
		{
			WSingleFieldContainer<WBlock> parent = blockContainer().get();
			if(parent != null && parent.accepts(this) != BlockDropAction.ACCEPT)
				eject();
		});
		this.isPaletteBlock = isPaletteBlock;
		color.set(defaultColor);
		table = new ArrayList<>();
		getCell(0, 0);
	}
	@SuppressWarnings("unchecked")
	@Override
	public final void addNotify(PFieldImmutable<?> newField)
	{
		if(container.get() != null)
			System.err.println("Warning: "+this+" was added to: "+newField.getOwner()+" without being removed from previous container: "+container.get());
		container.set((WSingleFieldContainer<WBlock>) newField.getOwner());
		addNotify2();
	}
	protected void addNotify2(){}
	@Override
	public final void removeNotify(PFieldImmutable<?> fromField)
	{
		//System.out.println(this+": removeNotify from "+fromField.getOwner());
		if(fromField.getOwner() == container.get())
			container.set(null);
		else
			System.err.println("Warning: "+this+" was removed from unknown container: "+fromField.getOwner()+"(current container="+container.get()+")");
		removeNotify2();
	}
	protected void removeNotify2(){}
	
	@Override
	public Pane getUI()
	{
		if(pane == null)
		{
			pane = new FXBlock(this);
		}
		return pane;
	}
	@Override
	public final WContainer<?> getParent()
	{
		return container.get();
	}
	
	public boolean isPaletteBlock()
	{
		return isPaletteBlock;
	}
	@Override
	public void putTargetSlots(Queue<Slot> sq)
	{
		if(isPaletteBlock())
			return;
		
		for(ArrayList<WCell> a : table)
		{
			for(WCell c : a)
			{
				c.putTargetSlots(sq);
			}
		}
	}
	
	@Override
	public void setLocation(int x, int y)
	{
		pane.setBlockLocation(x,y);
		/* Layout is broken, don't use it
		 * pane.setLayoutX(x);
		 * pane.setLayoutY(y);*/
	}
	@Override
	public int getX()
	{
		return pane.getRealX();
	}
	@Override
	public int getY()
	{
		return pane.getRealY();
	}
	
	
	@Override
	public PropertyReadOnly<WSingleFieldContainer<WBlock>> blockContainer()
	{
		return container;
	}
	
	@Override
	public ObjectProperty<Color> color()
	{
		return color;
	}
	public int getNumRows()
	{
		return table.size();
	}
	public int getNumColumnsAt(int row)
	{
		return table.get(row).size();
	}
	
	@Override
	public WCell getCell(int x, int y)
	{
		//Need to add new rows until we have the indicated row(y)
		while(y >= table.size())
		{
			table.add(new ArrayList<WCell>());
		}
		//The uppermost row must always have as many or more elements as the rest
		ArrayList<WCell> upperRow = table.get(0);
		int tx = upperRow.size();
		while(x > tx)
		{
			WCell c = new WCell(tx,0, this);
			upperRow.add(c);
			tx++;
			for(CellListener l : cellListeners)
				l.cellAdded(c);			
		}
		ArrayList<WCell> row = table.get(y);
		int cx = row.size();
		while(x >= cx)
		{
			WCell c = new WCell(cx,y, this);
			row.add(c);
			cx++;
			for(CellListener l : cellListeners)
				l.cellAdded(c);
		}
		return row.get(x);
	}
	@Override
	public void addCellListener(CellListener l)
	{
		cellListeners.add(l);
	}
	@Override
	public boolean removeCellListener(CellListener l)
	{
		return cellListeners.remove(l);
	}
	@Override
	public WRoot getRoot()
	{
		return root;
	}
	@Override
	public Property<TypeNode> tabType()
	{
		return tabType;
	}
	
	@Override
	public WBlock getBlockAt(Point2D localPoint)
	{
		if(((FXBlock)getUI()).blockContains(localPoint))
		{
			for(ArrayList<WCell> a : table)
			{
				for(WCell c : a)
				{
					Pane ui = c.getUI();
					Point2D cellPoint = FXUtil.fromTo(pane, ui, localPoint);
					WBlock blk = c.getBlockAt(cellPoint);
					if(blk != null)
						return blk;
				}
			}
			return this;
		}
		return null;
	}
	
	@Override
	public void eject()
	{
		WPane p = getWPane();
		FXBlock oldUI = (FXBlock)getUI();
		Point2D pt = FXUtil.fromTo(oldUI, p.getUI(), new Point2D(50, 50));
		
		blockContainer().get().getChildren().remove(this);
		if(p.accepts(this) == BlockDropAction.ACCEPT)
		{
			p.getChildren().add(this);
			setLocation((int)pt.getX(), (int)pt.getY());
			oldUI.toFront();
		}
	}
	@Override
	public WBlock makePointerBlock()
	{
		return new WPointerBlock(this);
	}
	
	@Override
	public void dispose()
	{
		for(ArrayList<WCell> a : table)
		{
			for(WCell c : a)
			{
				c.dispose();
			}
		}
		destructionListeners.fireEvents();
	}
}
